﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml.Linq;
using Netkk.Common.Models;
using Netkk.Library.Services;

namespace Netkk.Library.Repositories
{
    public class TwitterRepository : ITwitterRepository
    {

        private const string _userTimeLineUrl = "http://twitter.com/statuses/user_timeline.atom";
        private const string _updateStatusUrl = "http://twitter.com/statuses/update.xml";
        private ITwitterAuthenticationService _twitterAuthentication;

        public TwitterRepository(ITwitterAuthenticationService twitterAuthentication)
        {
            _twitterAuthentication = twitterAuthentication;
        }

        #region ITwitterRepository Members

        /// <summary>
        /// Gets the timeline from a specific user.
        /// </summary>
        /// <param name="count">The count, how much statuse to receive.</param>
        /// <returns>An IEnumerable of <see cref="Tweet" /> objects.</returns>
        public IEnumerable<Tweet> GetUserTimeLine(int count)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_userTimeLineUrl);
            request.Credentials = new NetworkCredential(
                _twitterAuthentication.Username,
                _twitterAuthentication.Passwort);

            string content;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                {
                    content = reader.ReadToEnd();
                }
            }

            XDocument doc = XDocument.Parse(content);
            XNamespace ns = "http://www.w3.org/2005/Atom";

            var query = from t in doc.Element(ns, "feed").Elements(ns, "entry")
                        select new Tweet(
                            (string)t.Element(ns, "id"),
                            (DateTime)t.Element(ns, "published"),
                            (string)t.Element(ns, "content")
                            );

            return query.Take(count);
        }

        /// <summary>
        /// Gets the formatted timeline from a specifit user.
        /// </summary>
        /// <param name="count">The count, how much statuse to receive.</param>
        /// <returns>An IEnumerable of string objects.</returns>
        public IEnumerable<string> GetFormattedUserTimeLine(int count)
        {
            IEnumerable<Tweet> tweets = GetUserTimeLine(count).Take(count);

            foreach (Tweet tweet in tweets)
            {
                string status = tweet.Status;

                status = ReplaceLinks(status);

                status = RemoveUserName(status);
                status = ReplaceReplyLinks(status);
                status = ReplaceHashLinks(status);
                status += CreateStatusLink(tweet);

                yield return status;
            }
        }

        /// <summary>
        /// Updates the twitter status from a given user.
        /// </summary>
        /// <param name="status">The new status.</param>
        public void UpdateStatus(string status)
        {
            // Cut status if needed.
            if (status.Length > 140)
            {
                status.Substring(0, 140);
            }

            string data = string.Format("?status={0}", status);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Concat(_updateStatusUrl, data));

            request.Credentials = new NetworkCredential(_twitterAuthentication.Username, _twitterAuthentication.Passwort);

            // Set values for the request back 
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = 0;

            request.GetResponse().Close();
        }

        #endregion

        /// <summary>
        /// Replaces all "@XYZ" strings to links to twitter.
        /// </summary>
        /// <param name="message">The status.</param>
        /// <returns>The formatted status.</returns>
        private string ReplaceReplyLinks(string message)
        {
            MatchCollection coll = Regex.Matches(message, "@[a-z0-9]*", RegexOptions.IgnoreCase);
            foreach (Match match in coll)
            {
                // @ vom Namen wegschneiden.
                string name = match.Value.Substring(1);
                message = message.Replace(match.Value,
                                string.Format("@<a href=\"http://www.twitter.com/{0}/\">{1}</a>",
                                              HttpUtility.UrlEncode(name),
                                              name));
            }

            return message;
        }

        /// <summary>
        /// Replaces all "#hash" hash links to twitter search links.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>The formatted message.</returns>
        private string ReplaceHashLinks(string message)
        {
            MatchCollection coll = Regex.Matches(message, "#[a-z0-9]*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
            foreach (Match match in coll)
            {
                // # vom Audruck wegschneiden
                string value = match.Value;
                string hash = value.Substring(1);
                message = message.Replace(match.Value,
                                          string.Format("#<a href=\"http://search.twitter.com/search?q={0}\">{1}</a>",
                                                        HttpUtility.UrlEncode(value),
                                                        hash));
            }

            return message;
        }

        /// <summary>
        /// Replaces all links to html.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>The message with replaced links.</returns>
        private string ReplaceLinks(string message)
        {
            MatchCollection coll = Regex.Matches(message,
                                                 @"http(s)?://([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&amp;\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?",
                                                 RegexOptions.IgnoreCase | RegexOptions.Compiled);
            foreach (Match match in coll)
            {
                string value = match.Value;
                message = message.Replace(match.Value, string.Format("<a href=\"{0}\">{0}</a>", value));
            }

            return message;
        }

        /// <summary>
        /// Builds a link from the message id and a linktext (e.g. "vor 4 Sekunden").
        /// </summary>
        /// <param name="tweet">The Tweet.</param>
        /// <returns>The formatted link.</returns>
        private string CreateStatusLink(Tweet tweet)
        {
            Match url = Regex.Match(tweet.Id, "http://twitter.com/[a-z]+/statuses/[0-9]+", RegexOptions.IgnoreCase | RegexOptions.Compiled);

            return string.Format(" <a href=\"{0}\">{1}</a>", url.Value, ToRelativeDate(tweet.DatePublished));
        }

        /// <summary>
        /// Removes the username at the beginning.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>The message without username at the beginnning.</returns>
        private string RemoveUserName(string message)
        {
            Match match = Regex.Match(message, "[a-z]*:", RegexOptions.IgnoreCase | RegexOptions.Compiled);

            return message.Replace(match.Value, String.Empty);
        }

        /// <summary>
        /// Parses a date to a string e.g. "vor 4 Sekunden".
        /// </summary>
        /// <param name="datePublished">The publish date.</param>
        /// <returns>The relative date string e.g. "vor 4 Sekunden".</returns>
        private string ToRelativeDate(DateTime datePublished)
        {
            TimeSpan timeSpan = DateTime.Now - datePublished;
            const string format = "vor {0} {1}";

            if (timeSpan <= TimeSpan.FromSeconds(60))
            {
                return string.Format(format,
                                     timeSpan.Seconds,
                                     timeSpan.Seconds > 1 ? "Sekunden" : "Sekunde");
            }

            if (timeSpan <= TimeSpan.FromMinutes(60))
            {
                return string.Format(format,
                                     timeSpan.Minutes,
                                     timeSpan.Minutes > 1 ? "Minuten" : "Minute");
            }

            if (timeSpan <= TimeSpan.FromHours(24))
            {
                return string.Format(format,
                                     timeSpan.Hours,
                                     timeSpan.Hours > 1 ? "Stunden" : "Stunde");
            }

            if (timeSpan <= TimeSpan.FromDays(30))
            {
                return string.Format(format,
                                     timeSpan.Days,
                                     timeSpan.Days > 1 ? "Tagen" : "Tag");
            }

            if (timeSpan <= TimeSpan.FromDays(365))
            {
                return string.Format(format,
                                     timeSpan.Days / 30,
                                     timeSpan.Days / 30 > 1 ? "Monaten" : "Monat");
            }

            return string.Format(format,
                                 timeSpan.Days / 365,
                                 timeSpan.Days / 365 > 1 ? "Jahren" : "Jahr");


        }
    }
}